package com.yeskery.nut.script.function;

import com.yeskery.nut.script.parser.ScriptParseException;
import com.yeskery.nut.util.StringUtils;

import java.util.Collection;
import java.util.Stack;
import java.util.function.Supplier;

/**
 * 函数执行器
 * @author sprout
 * @version 1.0
 * 2022-05-15 21:16
 */
public class DefaultFunctionExecutor implements FunctionExecutor {

    /** 函数转换器 */
    private final FunctionParser functionParser;

    /**
     * 构建函数执行器
     * @param functionParser 函数转换器
     */
    public DefaultFunctionExecutor(FunctionParser functionParser) {
        this.functionParser = functionParser;
    }

    @Override
    public String execute(String line) {
        return doExecute(line, () -> functionParser.parse(line));
    }

    @Override
    public String execute(String line, FunctionContext extendFunctionContext) {
        return doExecute(line, () -> functionParser.parse(line, extendFunctionContext));
    }

    /**
     * 函数执行
     * @param line 字符串行
     * @param stackSupplier 函数元数据栈
     * @return 函数执行结果
     */
    private String doExecute(String line, Supplier<Collection<Stack<FunctionMetadataIndex>>> stackSupplier) {
        if (StringUtils.isEmpty(line)) {
            return line;
        }

        boolean firstDeal = true;
        StringBuilder stringBuilder = new StringBuilder(line);
        Collection<Stack<FunctionMetadataIndex>> stackCollection = stackSupplier.get();
        for (Stack<FunctionMetadataIndex> stack : stackCollection) {
            FunctionMetadataIndex lastFunctionMetadataIndex = null;
            int offset = 0;
            int previousLength = stringBuilder.length();
            boolean clearOffset = false;
            while (!stack.empty()) {
                FunctionMetadataIndex functionMetadataIndex = stack.pop();
                int startIndex = firstDeal ? functionMetadataIndex.getStartIndex() : functionMetadataIndex.getStartIndex() - offset;
                int endIndex = functionMetadataIndex.getEndIndex();
                if (lastFunctionMetadataIndex != null && lastFunctionMetadataIndex.getParentFunctionMetadata() == functionMetadataIndex.getFunctionMetadata()) {
                    if (offset == 0 && stack.isEmpty()) {
                        offset = line.length() - stringBuilder.length();
                    }
                    endIndex -= offset;
                    clearOffset = true;
                    offset = 0;
                }
                if (!clearOffset) {
                    previousLength = stringBuilder.length();
                }
                if (startIndex >= endIndex) {
                    throw new ScriptParseException("Invalid Script Expression Function At Line Expression: [" + line + "]");
                }

                String expression = stringBuilder.substring(startIndex + 1, endIndex);
                FunctionMetadata functionMetadata = functionMetadataIndex.getFunctionMetadata();
                functionMetadata.setExpression(expression);
                Function function = functionMetadata.getFunction();
                try {
                    functionMetadata.setExecuteResult(function.apply(expression));
                } catch (Exception e) {
                    throw new FunctionException("Function [" + function.getName() + "] Apply Fail.", e, function);
                }
                int insertIndex = startIndex - 1 - functionMetadata.getFunction().getName().length();
                stringBuilder.delete(insertIndex, endIndex + 1);
                stringBuilder.insert(insertIndex, functionMetadata.getExecuteResult());
                if (clearOffset) {
                    clearOffset = false;
                    previousLength = stringBuilder.length();
                } else {
                    offset += previousLength - stringBuilder.length();
                }
                lastFunctionMetadataIndex = functionMetadataIndex;
            }
            firstDeal = false;
        }
        return stringBuilder.toString();
    }
}
